home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / share / python-support / update-notifier-kde / update-notifier-kde.py < prev   
Encoding:
Python Source  |  2008-10-23  |  19.1 KB  |  400 lines

  1. #!/usr/bin/env python
  2.  
  3. #############################################################################
  4. ##
  5. ## Copyright 2007-2008 Canonical Ltd
  6. ## Author: Jonathan Riddell <jriddell@ubuntu.com>
  7. ##
  8. ## This program is free software; you can redistribute it and/or
  9. ## modify it under the terms of the GNU General Public License as
  10. ## published by the Free Software Foundation; either version 2 of 
  11. ## the License, or (at your option) any later version.
  12. ##
  13. ## This program is distributed in the hope that it will be useful,
  14. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16. ## GNU General Public License for more details.
  17. ##
  18. ## You should have received a copy of the GNU General Public License
  19. ## along with this program.  If not, see <http://www.gnu.org/licenses/>.
  20. ##
  21. #############################################################################
  22.  
  23. import sys, os, time
  24.  
  25. from PyQt4.QtCore import *
  26. from PyQt4.QtGui import *
  27. from PyQt4 import uic
  28. from PyKDE4.kdecore import *
  29. from PyKDE4.kdeui import *
  30. from PyKDE4.kio import *
  31.  
  32. import apt_check
  33.  
  34. def translate(self, prop):
  35.     """reimplement method from uic to change it to use gettext"""
  36.     if prop.get("notr", None) == "true":
  37.         return self._cstring(prop)
  38.     else:
  39.         if prop.text is None:
  40.             return ""
  41.         text = prop.text.encode("UTF-8")
  42.         return i18n(text)
  43.  
  44. uic.properties.Properties._string = translate
  45.  
  46. from UpdateManager.Core.MetaRelease import MetaReleaseCore
  47. from UpdateManager.DistUpgradeFetcherKDE import DistUpgradeFetcherKDE
  48. import time
  49.  
  50. class App(QObject):
  51.     """an applet to show a systray icon when apt has software updates to be installed, when Apport has crash reports, for reboot notification and for upgrade hook messages"""
  52.     def __init__(self, distUpgrade=False, useDevelopmentRelease=False, useProposed=False):
  53.         QObject.__init__(self)
  54.         if distUpgrade or useDevelopmentRelease or useProposed:
  55.             self.upgradeMode = True
  56.             self.newReleaseCheck(useDevelopmentRelease, useProposed)
  57.         else:
  58.             self.upgradeMode = False
  59.             self.init()
  60.  
  61.     def init(self):
  62.         self.tray = KSystemTrayIcon(KIcon("system-software-update"))
  63.         self.connect(self.tray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.activated)
  64.         self.dirWatch = KDirWatch(self)
  65.         self.dirWatch.addDir("/var/lib/apt/lists/") #apt-get update has been run
  66.         self.dirWatch.addFile("/var/lib/update-notifier/dpkg-run-stamp") #something has been installed
  67.         self.connect(self.dirWatch, SIGNAL("dirty(const QString &)"), self.aptDirectoryChanged)
  68.         QTimer.singleShot(5000, self.aptDirectoryChanged) #run check shortly after startup
  69.  
  70.         try:
  71.             pipe = os.popen("lsb_release --id -s")
  72.             self.distroName = pipe.read().strip()
  73.             del pipe
  74.             if self.distroName == "Ubuntu":
  75.                 self.distroName = "Kubuntu"
  76.         except Exception, e:
  77.             self.distroName = i18nc("fallback distro name", "distribution release")
  78.  
  79.         self.rebootTray = KSystemTrayIcon(KIcon("system-restart"))
  80.         self.connect(self.rebootTray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.rebootActivated)
  81.         self.rebootWatch = KDirWatch(self)
  82.         self.rebootWatch.addDir("/var/run/")
  83.         self.rebootWatch.addDir("/var/lib/update-notifier/")
  84.         self.connect(self.rebootWatch, SIGNAL("dirty(const QString &)"), self.rebootRequired)
  85.  
  86.         if QFile.exists("/usr/share/apport/apport-qt"):
  87.             self.apportWatch = KDirWatch(self)
  88.             self.apportWatch.addDir("/var/crash/")
  89.             self.connect(self.apportWatch, SIGNAL("dirty(const QString &)"), self.runApport)
  90.             self.showApportIcon()
  91.  
  92.         self.hooksTray = KSystemTrayIcon(KIcon("help-hint"))
  93.         self.hooksWatch = KDirWatch(self)
  94.         self.hooksWatch.addDir("/var/lib/update-notifier/user.d/")
  95.         self.connect(self.hooksWatch, SIGNAL("dirty(const QString &)"), self.processUpgradeHooks)
  96.         self.processUpgradeHooks()
  97.  
  98.     def newReleaseCheck(self, useDevelopmentRelease, useProposed):
  99.         """ check for updates when user asks for --devel-release or --proposed, if there are any show the wizard """
  100.         metaRelease = MetaReleaseCore(useDevelopmentRelease, useProposed)
  101.         while metaRelease.downloading:
  102.             time.sleep(1)
  103.         self.new_dist = metaRelease.new_dist
  104.         self.trayToolTip = ""
  105.         showTray = False
  106.         if self.new_dist is not None:
  107.             self.fetcher = DistUpgradeFetcherKDE(useDevelopmentRelease, useProposed) #runs sys.exit() if no result
  108.             QTimer.singleShot(10, self.runDistUpgrade)
  109.         else:
  110.             KMessageBox.sorry(None, i18n("No new upgrade available"), i18n("No Upgrade"))
  111.             sys.exit()
  112.  
  113.     def runDistUpgrade(self):
  114.         result = self.fetcher.run()
  115.         if self.upgradeMode == True and (result is None or result == False):
  116.             sys.exit()
  117.  
  118.     #update checking
  119.     def aptDirectoryChanged(self, path=None):
  120.         """ check for updates, if there are any show the tray icon """
  121.         metaRelease = MetaReleaseCore()
  122.         while metaRelease.downloading:
  123.             time.sleep(1)
  124.         self.new_dist = metaRelease.new_dist
  125.         # a previous run may have enabled tooltip. reset if not visible only
  126.         if not self.tray.isVisible():
  127.             self.trayToolTip = ""
  128.         showTray = False
  129.         if self.new_dist is not None:
  130.             self.trayToolTip = i18nc("distro name, version number", "An upgrade to %1 %2 is available.", self.distroName, self.new_dist.version)
  131.             showTray = True
  132.             menu = self.tray.contextMenu()
  133.             if not self.tray.isVisible():
  134.                 upgradeAction = menu.addAction(i18nc("distro name, version number", "Upgrade to %1 %2", self.distroName, self.new_dist.version))
  135.                 self.connect(upgradeAction, SIGNAL("triggered(bool)"), self.upgradeActionTriggered)
  136.  
  137.         result = apt_check.run()
  138.         if result[1] > 0:
  139.             #security updates available, show different icon?
  140.             pass
  141.         if result[0] > 0:
  142.             showTray = True
  143.             if not self.tray.isVisible():
  144.                 self.updates = result[0]
  145.                 QTimer.singleShot(1000, self.showMessage) #delay so it is shown when tray icon in correct position
  146.                 self.trayToolTip = i18np("A software update is available", "%1 software updates are available", self.updates)
  147.                 if self.new_dist is not None:
  148.                     self.trayToolTip = i18np("A software update is available.\nAn upgrade to %2 %3 is also available.", "%1 software updates are available.\nAn upgrade to %2 %3 is also available.", self.updates, self.distroName, self.new_dist.version)
  149.         self.tray.setToolTip(self.trayToolTip)
  150.         self.tray.setVisible(showTray)
  151.  
  152.     def showMessage(self):
  153.         # FIXME can only use KPassivePopup as a static method with systray but that means
  154.         # we can't connect to the clicked() slot
  155.         KPassivePopup.message(KPassivePopup.Balloon, i18n("Software Updates"), self.trayToolTip, KIcon("system-software-update").pixmap(QSize(22,22)), self.tray)
  156.  
  157.     def upgradeActionTriggered(self, checked):
  158.         """called from tray icon right click menu"""
  159.         self.fetcher = DistUpgradeFetcherKDE()
  160.         QTimer.singleShot(10, self.runDistUpgrade)
  161.  
  162.     def activated(self, activationReason):
  163.         """run adept when icon is clicked on"""
  164.         if activationReason == QSystemTrayIcon.Trigger:
  165.             doingDistUpgrade = False
  166.             if self.new_dist is not None:
  167.                 distUpgrade = KMessageBox.questionYesNo(None, i18n("Do you want to do a full upgrade to the new distribution release %1?", self.new_dist.version), i18n("Full Upgrade?"), KStandardGuiItem.yes(), KStandardGuiItem.no(), "distUpgrade")
  168.                 if distUpgrade == KMessageBox.Yes:
  169.                     doingDistUpgrade = True
  170.                     self.fetcher = DistUpgradeFetcherKDE()
  171.                     QTimer.singleShot(10, self.runDistUpgrade)
  172.             if not doingDistUpgrade:
  173.                 KProcess.startDetached("kdesudo", ["adept", "updater"])
  174.  
  175.     #reboot notification
  176.     def rebootRequired(self, path=None):
  177.         if QFile.exists("/var/run/reboot-required") and QFile.exists("/var/lib/update-notifier/dpkg-run-stamp") and not self.rebootTray.isVisible():
  178.             self.rebootTray.show()
  179.             QTimer.singleShot(1000, self.showRebootMessage) #delay so it is shown when tray icon in correct position
  180.             self.rebootTray.setToolTip(i18n("Newly updated software needs your system to be rebooted before it can be used."))
  181.  
  182.     def showRebootMessage(self):
  183.         # FIXME can only use KPassivePopup as a static method with systray but that means
  184.         # we can't connect to the clicked() slot
  185.         KPassivePopup.message(KPassivePopup.Balloon, i18n("Reboot Required"), i18n("Newly updated software needs your system to be rebooted before it can be used."), KIcon("system-restart").pixmap(QSize(22,22)), self.rebootTray)
  186.  
  187.     def rebootActivated(self, activationReason):
  188.         if activationReason == QSystemTrayIcon.Trigger:
  189.             KProcess.startDetached("qdbus", ["org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface.logout", "1", "1", "3"]) #ShutdownConfirmYes ShutdownTypeReboot ShutdownModeInteractive - see README.kworkspace for other possibilities
  190.  
  191.     # Apport
  192.     def runApport(self):
  193.         time.sleep(3) #sleep briefly, else we are too fast for apport
  194.         result = os.system("/usr/share/apport/apport-checkreports --system")
  195.         if result == 0:
  196.             KProcess.startDetached("kdesudo", ["/usr/share/apport/apport-qt"])
  197.         else:
  198.             KProcess.startDetached("/usr/share/apport/apport-qt")
  199.  
  200.         # make sure we hide something that exists
  201.         if self.apportTray is not None:
  202.             self.apportTray.hide()
  203.  
  204.     def showApportIcon(self):
  205.         """ run on startup, show apport icon if crashes are outstanding """
  206.         result = os.system("/usr/share/apport/apport-checkreports")
  207.         if result == 0:
  208.             self.apportTray = KSystemTrayIcon(KIcon("apport"))
  209.             self.connect(self.apportTray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.apportActivated)
  210.             self.apportTray.show()
  211.             QTimer.singleShot(2000, self.showApportMessage) #delay so it is shown when tray icon in correct position
  212.             self.crashMessage = i18n("An application has crashed on your "
  213.                                      "system (now or in the past).\n"
  214.                                      "Click to "
  215.                                      "display details. ")
  216.             self.apportTray.setToolTip(self.crashMessage)
  217.         else:
  218.             # prevent AttributeError exception if apportTray was not created above
  219.             self.apportTray = None
  220.  
  221.     def showApportMessage(self):
  222.         # FIXME can only use KPassivePopup as a static method with systray but that means
  223.         # we can't connect to the clicked() slot
  224.         KPassivePopup.message(KPassivePopup.Balloon, i18n("Crash Handler"), self.crashMessage, KIcon("apport").pixmap(QSize(22,22)), self.apportTray)
  225.  
  226.     def apportActivated(self, activationReason):
  227.         if activationReason == QSystemTrayIcon.Trigger:
  228.             self.runApport()
  229.  
  230.     #upgrade hooks, see https://wiki.kubuntu.org/InteractiveUpgradeHooks
  231.     def processUpgradeHooks(self):
  232.         """show systray icon if upgrade hook messages need to be shown"""
  233.         files = os.listdir("/var/lib/update-notifier/user.d/")
  234.         self.fileInfos = {}
  235.         for file in files:
  236.             fileResult = self.processUpgradeHook(file)
  237.             if fileResult != None:
  238.                 self.fileInfos[file] = fileResult
  239.                 self.fileInfos[file]["fileName"] = file
  240.         if len(self.fileInfos) > 0:
  241.             self.connect(self.hooksTray, SIGNAL("activated( QSystemTrayIcon::ActivationReason )"), self.hooksActivated)
  242.             self.hooksTray.setToolTip(i18n("Software upgrade notifications are available"))
  243.             self.hooksTray.show()
  244.             QTimer.singleShot(2000, self.showHooksMessage) #delay so it is shown when tray icon in correct position
  245.  
  246.     def processUpgradeHook(self, file):
  247.         """parse an upgrade hook file and return it if it should be shown"""
  248.         fileHandle = open("/var/lib/update-notifier/user.d/" + file)
  249.         fileInfo = {}
  250.         for line in fileHandle:
  251.             colon = line.find(":")
  252.             space = line.find(" ")
  253.             if (colon > 0 and space == -1) or (colon != -1 and colon < space):
  254.                 list = line.split(":", 1)
  255.                 key = list[0]
  256.                 fileInfo[key] = list[1].strip()
  257.             else:
  258.                 fileInfo[key] += line
  259.  
  260.         #check if seen already
  261.         config = KGlobal.config()
  262.         configGroup = config.group("updateNotifications")
  263.         alreadySeen = configGroup.readEntry(file, QVariant(False))
  264.         if alreadySeen.toBool():
  265.             return
  266.  
  267.         #check if before a reboot
  268.         if "DontShowAfterReboot" in fileInfo and fileInfo["DontShowAfterReboot"] == "True":
  269.             uptimeFile = open("/proc/uptime")
  270.             uptimeLine = uptimeFile.readline()
  271.             uptime = float(uptimeLine.split(" ")[0])
  272.             now = time.time()
  273.             stat = os.stat("/var/lib/update-notifier/user.d/" + file)
  274.             #if((int)uptime > 0 && (now - mtime) > (int)uptime) { then skip
  275.             if uptime > 0 and ((now - stat.st_mtime) > uptime):
  276.                 return
  277.         #check if DisplayIf true
  278.         if "DisplayIf" in fileInfo:
  279.             display = os.system(fileInfo["DisplayIf"])
  280.             if display != 0:
  281.                 return
  282.  
  283.         #we want to show this one, return the dictionary
  284.         return fileInfo
  285.  
  286.     def showHooksMessage(self):
  287.         # FIXME can only use KPassivePopup as a static method with systray but that means
  288.         # we can't connect to the clicked() slot
  289.         KPassivePopup.message(KPassivePopup.Balloon, i18n("Upgrade Information"), i18n("Software upgrade notifications are available"), KIcon("help-hint").pixmap(QSize(22,22)), self.hooksTray)
  290.  
  291.     def hooksActivated(self, activationReason):
  292.         """show the dialogue with messages when user clicks on upgrade hooks systray icon"""
  293.         if activationReason == QSystemTrayIcon.Trigger:
  294.             self.hooksTray.hide()
  295.             dialogue = QDialog()
  296.             if os.path.exists("hooks-dialogue.ui"):
  297.                 APPDIR = QDir.currentPath()
  298.             else:
  299.                 file = KStandardDirs.locate("appdata", "hooks-dialogue.ui")
  300.                 APPDIR = file.left(file.lastIndexOf("/"))
  301.             uic.loadUi(APPDIR + "/hooks-dialogue.ui", dialogue)
  302.             dialogue.image.setPixmap(KIcon("help-hint").pixmap(QSize(48,48)))
  303.             nextButton = dialogue.buttonBox.addButton(i18n("Next"), QDialogButtonBox.AcceptRole)
  304.             nextButton.setIcon(KIcon("go-next"))
  305.             runButton = dialogue.buttonBox.addButton(i18n("Run this action now"), QDialogButtonBox.ActionRole)
  306.             runButton.setIcon(KIcon("system-run"))
  307.             self.connect(runButton, SIGNAL("clicked()"), self.runHookCommand)
  308.             dialogue.buttonBox.button(QDialogButtonBox.Close).setIcon(KIcon("dialog-close"))
  309.             i = 0
  310.             for file in self.fileInfos:
  311.                 i = i + 1
  312.                 if len(self.fileInfos) == i:
  313.                     nextButton.hide()
  314.                 self.terminal = False
  315.                 if self.fileInfos[file].has_key("Command"):
  316.                     runButton.show()
  317.                     self.command = self.fileInfos[file]["Command"]
  318.                     if self.fileInfos[file].has_key("Terminal") and self.fileInfos[file]["Terminal"] == "True":
  319.                         self.terminal = True
  320.                 else:
  321.                     runButton.hide()
  322.                 language = KGlobal.locale().language()
  323.                 if self.fileInfos[file].has_key("Name"):
  324.                     if self.fileInfos[file].has_key("Name-" + language):
  325.                         text = self.fileInfos[file]["Name" + language]
  326.                     else:
  327.                         text = self.fileInfos[file]["Name"]
  328.                     dialogue.vbox_message.setText(i18n("<b>%1</b>", text))
  329.                 else:
  330.                     dialogue.vbox_message.setText("<b>Update Information</b>")
  331.                 if self.fileInfos[file].has_key("Description-" + language):
  332.                     text = self.fileInfos[file]["Description" + language]
  333.                 else:
  334.                     text = self.fileInfos[file]["Description"]
  335.                 text = text.replace("\n", "")
  336.                 if self.fileInfos[file].has_key("GettextDomain"):
  337.                     KGlobal.locale().insertCatalog(self.fileInfos[file]["GettextDomain"])
  338.                     text = i18n(text)
  339.                 dialogue.textview_hook.setText(text)
  340.                 result = dialogue.exec_()
  341.                 #save that it has been seen
  342.                 config = KGlobal.config()
  343.                 configGroup = config.group("updateNotifications")
  344.                 configGroup.writeEntry(file, QVariant(True))
  345.                 config.sync()
  346.                 if result == QDialog.Rejected:
  347.                     break
  348.  
  349.     def runHookCommand(self):
  350.         process = KProcess()
  351.         if self.terminal:
  352.             self.command = "konsole -e " + self.command
  353.         process.setShellCommand(self.command)
  354.         process.startDetached()
  355.  
  356. if __name__ == "__main__":
  357.     """start the application.  TODO, clever things here to not start the GUI until it has to"""
  358.     appName     = "update-notifier-kde"
  359.     catalogue   = "update-notifier-kde"
  360.     programName = ki18n("Update Notifier")
  361.     version     = "1.1"
  362.     description = ki18n("An applet to show a systray icon when apt has software updates to be installed, when Apport has crash reports, for reboot notification and for upgrade hook messages.")
  363.     license     = KAboutData.License_GPL
  364.     copyright   = ki18n("2008 Canonical Ltd")
  365.     text        = KLocalizedString()
  366.     homePage    = "http://launchpad.net/adept"
  367.     bugEmail    = ""
  368.  
  369.     aboutData   = KAboutData(appName, catalogue, programName, version, description,
  370.                                 license, copyright, text, homePage, bugEmail)
  371.  
  372.     aboutData.addAuthor(ki18n("Jonathan Riddell"), ki18n("Author"))
  373.  
  374.     options = KCmdLineOptions()
  375.     options.add("d").add("devel-release", ki18n("Offer to upgrade to the latest devel release, if possible"))
  376.     options.add("p").add("proposed", ki18n("Offer to upgrade using latest proposed version of the release upgrader, if possible"))
  377.     options.add("u").add("dist-upgrade", ki18n("Offer to upgrade to the latest release, if possible"))
  378.  
  379.     KCmdLineArgs.init(sys.argv, aboutData)
  380.     KCmdLineArgs.addCmdLineOptions(options)
  381.  
  382.     app = KApplication()
  383.     app.setQuitOnLastWindowClosed(False)
  384.     app.setWindowIcon(KIcon("system-software-update"))
  385.     if app.isSessionRestored():
  386.         sys.exit(1)
  387.     args = KCmdLineArgs.parsedArgs()
  388.     distUpgrade = False
  389.     useDevelopmentRelease = False
  390.     useProposed = False
  391.     if args.isSet("dist-upgrade"):
  392.         distUpgrade = True
  393.     if args.isSet("devel-release"):
  394.         useDevelopmentRelease = True
  395.     if args.isSet("proposed"):
  396.         useProposed = True
  397.  
  398.     applet = App(distUpgrade, useDevelopmentRelease, useProposed)
  399.     sys.exit(app.exec_())
  400.